package com.kelin.calendarlistview.library;

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.*;
import ohos.agp.utils.Color;
import ohos.agp.utils.Point;
import ohos.agp.utils.TextAlignment;
import ohos.agp.utils.TextTool;
import ohos.agp.window.service.Display;
import ohos.agp.window.service.DisplayManager;

import ohos.app.Context;

import ohos.global.resource.NotExistException;
import ohos.global.resource.WrongTypeException;

import ohos.multimodalinput.event.TouchEvent;

import java.io.IOException;

import java.util.Optional;

/**
 * CalendarListView
 */
public class CalendarListView extends StackLayout {
    private static final int WEEK_ITEM_TEXT_SIZE = 36;

    protected PinnedHeaderListView listView;
    protected CalendarView calendarView;
    protected DirectionalLayout weekBar;
    private String currentSelectedDate;
    private Status status = Status.LIST_CLOSE;

    protected BaseCalendarListAdapter calendarListAdapter;
    protected BaseCalendarItemAdapter calendarItemAdapter;

    private onListPullListener onListPullListener;
    private OnCalendarViewItemClickListener onCalendarViewItemClickListener;

    private float startY;
    private float downY;
    private float dy;

    /**
     * CalendarListView instance
     *
     * @param context 上下文
     * @param attrs 属性
     */
    public CalendarListView(Context context, AttrSet attrs) {
        super(context, attrs);
        CalendarHelper.init(context);
        LayoutScatter.getInstance(context).parse(ResourceTable.Layout_layout_calendar_listview, this, true);
        listView = (PinnedHeaderListView) findComponentById(ResourceTable.Id_listview);
        final StackLayout frameLayout = (StackLayout) findComponentById(ResourceTable.Id_calendar_view_container);
        calendarView = new CalendarView(context);
        frameLayout.addComponent(calendarView, new ComponentContainer.LayoutConfig(ComponentContainer.LayoutConfig.MATCH_PARENT, ComponentContainer.LayoutConfig.MATCH_PARENT));
        initListener();
        initWeekBar();
    }


    /**
     * setCalendarListViewAdapter
     *
     * @param calendarItemAdapter this adapter is for CalendarView
     * @param calendarListAdapter this adapter is for ListView
     */
    public void setCalendarListViewAdapter(BaseCalendarItemAdapter calendarItemAdapter, final BaseCalendarListAdapter calendarListAdapter) {
        this.calendarListAdapter = calendarListAdapter;
        this.calendarItemAdapter = calendarItemAdapter;
        this.listView.setItemProvider(calendarListAdapter);
        this.calendarView.setCalendarItemAdapter(calendarItemAdapter);
        calendarListAdapter.registerDataSetObserver(() -> {
            if (TextTool.isNullOrEmpty(currentSelectedDate)) {
                currentSelectedDate = calendarListAdapter.getDateBySection(0);
                int diffMonth = CalendarHelper.getDiffMonthByTime(calendarView.getSelectedDate(), currentSelectedDate);
                if (diffMonth != 0) {
                    calendarView.changeMonth(diffMonth, currentSelectedDate, status);
                }
                CalendarListView.this.calendarView.animateSelectedViewToDate(currentSelectedDate);
            }

            //when data changed,make sure the ListView scroll to last item's position.
            int listPos = CalendarListView.this.calendarListAdapter.getDataListIndexByDate(currentSelectedDate);
            if (listPos >= 0) {
                listView.smoothScrollToPosition(listPos, -1);
            }
            listView.setRefreshing(false);
            if (listView.getLoadingFooter().getState() == LoadingFooter.State.Loading) {
                listView.getLoadingFooter().setState(LoadingFooter.State.Idle);
            }
        });
    }

    /**
     * getCurrentSelectedDate
     *
     * @return String
     */
    public String getCurrentSelectedDate() {
        return currentSelectedDate;
    }

    /**
     * setOnMonthChangedListener
     *
     * @param onMonthChangedListener OnMonthChangedListener
     */
    public void setOnMonthChangedListener(OnMonthChangedListener onMonthChangedListener) {
        calendarView.setOnMonthChangedListener(onMonthChangedListener);
    }

    /**
     * setOnListPullListener
     *
     * @param onListPullListener onListPullListener
     */
    public void setOnListPullListener(onListPullListener onListPullListener) {
        this.onListPullListener = onListPullListener;
    }

    /**
     * setOnCalendarViewItemClickListener
     *
     * @param onCalendarViewItemClickListener onCalendarViewItemClickListener
     */
    public void setOnCalendarViewItemClickListener(OnCalendarViewItemClickListener onCalendarViewItemClickListener) {
        this.onCalendarViewItemClickListener = onCalendarViewItemClickListener;
    }

    /**
     * setWeekBar
     *
     * @param weekBar DirectionalLayout
     */
    public void setWeekBar(DirectionalLayout weekBar) {
        this.weekBar = weekBar;
    }

    /**
     * getWeekBar
     *
     * @return DirectionalLayout
     */
    public DirectionalLayout getWeekBar() {
        return weekBar;
    }

    private void initCalendarViewListener() {
        calendarView.setLayoutRefreshedListener(new LayoutRefreshedListener() {
            @Override
            public void onRefreshed(Component component) {
                int listViewHeight = CalendarListView.this.getHeight() - weekBar.getHeight() - CalendarView.getmItemHeight();
                DirectionalLayout.LayoutConfig layoutConfig = (DirectionalLayout.LayoutConfig) listView.getLayoutConfig();
                layoutConfig.height = listViewHeight;
                listView.setLayoutConfig(layoutConfig);
            }
        });

        calendarView.setOnDateSelectedListener(new CalendarView.OnDateSelectedListener() {
            @Override
            public void onDateSelected(CalendarView calendarView, Component view, String time, int pos) {
                if (!listView.smoothScrolling) {
                    currentSelectedDate = time;
                    int listPos = calendarListAdapter.getDataListIndexByDate(time);
                    OnCalendarViewItemClickListener.SelectedDateRegion selectedDateRegion = calendarListAdapter.getSelectedDateTypeByDate(time);
                    if (selectedDateRegion == OnCalendarViewItemClickListener.SelectedDateRegion.INSIDE) {
                        listView.smoothScrollToPosition(listPos, 0);
                    }

                    if (onCalendarViewItemClickListener != null) {
                        onCalendarViewItemClickListener.onDateSelected(view, time, listPos, selectedDateRegion);
                    }
                }
            }
        });
    }

    private void initListViewListener() {
        listView.setOnSectionChangedListener(new PinnedHeaderListView.OnSectionChangedListener() {
            @Override
            public void onSectionChanged(int newSection, int oldSection) {
                currentSelectedDate = calendarListAdapter.getDateBySection(newSection);
                String lastSelectDate = calendarListAdapter.getDateBySection(oldSection);
                int diffMonth = CalendarHelper.getDiffMonthByTime(lastSelectDate, currentSelectedDate);
                if (diffMonth == 0) {
                    diffMonth = CalendarHelper.getDiffMonthByTime(calendarView.getSelectedDate(), currentSelectedDate);
                }
                changeMonth(diffMonth);
            }
        });

        listView.addOnScrollListener(new Component.ScrolledListener() {
            @Override
            public void onContentScrolled(Component component, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {

            }

            @Override
            public void scrolledStageUpdate(Component component, int scrollState) {
                if (scrollState == SCROLL_IDLE_STAGE) {
                    // correct CalendarView's selected Date same with ListView's currentSelecteddate
                    if (!calendarView.getSelectedDate().equals(currentSelectedDate)) {
                        int diffMonth = CalendarHelper.getDiffMonthByTime(calendarView.getSelectedDate(), currentSelectedDate);
                        changeMonth(diffMonth);
                    }
                }
            }
        });


        listView.setPullUpStateListener(new PinnedHeaderListView.PullUpStateListener() {

            @Override
            public void onLoadMore(PinnedHeaderListView listView) {
                onListPullListener.onLoadMore();
            }
        });
        listView.setPullDownStateListener(new PinnedHeaderListView.PullDownStateListener() {
            @Override
            public void onRefresh(PinnedHeaderListView listView) {
                listView.setRefreshing(true);
                onListPullListener.onRefresh();
            }
        });
    }

    private void setListViewTouchEvent(Component component, TouchEvent ev) {
        if (status == Status.DRAGGING) {
            if (ev.getAction() == TouchEvent.PRIMARY_POINT_UP || ev.getAction() == TouchEvent.CANCEL) {
                listView.getBody().setEnabled(true);
            } else {
                listView.getBody().setEnabled(false);
            }
            return;
        }
        if (ev.getAction() == TouchEvent.PRIMARY_POINT_DOWN || ev.getAction() == TouchEvent.PRIMARY_POINT_UP || ev.getAction() == TouchEvent.CANCEL) {
            listView.setOnTouchEvent(component, ev);
        } else {
            if (status == Status.LIST_OPEN && dy > weekBar.getBottom() + CalendarView.getmItemHeight()) {
                listView.setOnTouchEvent(component, ev);
            } else if (status == Status.LIST_CLOSE && dy > weekBar.getBottom() + calendarView.getBottom()) {
                listView.setOnTouchEvent(component, ev);
            }
        }
    }

    private void setCalendarViewTouchEvent(Component component, TouchEvent ev) {
        if (status == Status.LIST_CLOSE) {
            if (ev.getAction() == TouchEvent.PRIMARY_POINT_DOWN) {
                calendarView.setOnTouchEvent(component, ev);
            } else if (ev.getAction() == TouchEvent.PRIMARY_POINT_UP || ev.getAction() == TouchEvent.CANCEL) {
                if (dy < weekBar.getBottom() + calendarView.getBottom()) {
                    calendarView.setOnTouchEvent(component, ev);
                }
            }
        }
    }

    private void setPointUpAnimation(float curY, CalendarView.SelectedRowColumn selectedRowColumn) {
        if (curY < downY) {
            if (Math.abs(((double) curY - downY)) > calendarView.getHeight() / 2d) {
                animationToTop(selectedRowColumn);
            } else {
                animationToBottom();
            }
        } else {
            if (Math.abs(((double) curY - downY)) < calendarView.getHeight() / 2d) {
                animationToTop(selectedRowColumn);
            } else {
                animationToBottom();
            }

        }
    }

    private void setMoveTranslationY(float curY, CalendarView.SelectedRowColumn selectedRowColumn) {
        if (Math.abs(listView.getTranslationY()) >= calendarView.getHeight() - CalendarView.getmItemHeight()
                && curY < startY) {
            listView.setTranslationY(-(calendarView.getHeight() - CalendarView.getmItemHeight()));
        }
        if (curY > startY && listView.getTranslationY() >= 0) {
            listView.setTranslationY(0);
        }
        listView.setTranslationY((float) (listView.getTranslationY() + ((double)curY - startY)));
        calendarView.setTranslationY((float) ((double)calendarView.getTranslationY() + selectedRowColumn.row *
                ((double)curY - startY) / (((double)calendarView.getHeight() / CalendarView.getmItemHeight()) - 1)));
        startY = curY;
        status = Status.DRAGGING;
    }

    protected void initListener() {
        initCalendarViewListener();
        initListViewListener();
        setTouchEventListener((component, ev) -> {
            CalendarView.SelectedRowColumn selectedRowColumn = calendarView.getSelectedRowColumn();
            setListViewTouchEvent(component, ev);
            setCalendarViewTouchEvent(component, ev);
            switch (ev.getAction()) {
                case TouchEvent.PRIMARY_POINT_DOWN:
                    dy = ev.getPointerPosition(0).getY();
                    downY = ev.getPointerScreenPosition(0).getY();
                    // when listView is close (calendarView is not shrink)
                    if (status == Status.LIST_CLOSE && dy <= weekBar.getBottom() + calendarView.getBottom()) {
                        return true;
                    }
                    if (status == Status.LIST_OPEN && dy >= weekBar.getBottom() + CalendarView.getmItemHeight()) {
                        return true;
                    }

                    startY = ev.getPointerScreenPosition(0).getY();
                    break;
                case TouchEvent.POINT_MOVE:
                    float curY = ev.getPointerScreenPosition(0).getY();
                    if (status == Status.LIST_CLOSE && curY >= startY) {
                        return true;
                    }

                    if (status == Status.LIST_CLOSE && curY >= downY) {
                        return true;
                    }

                    if (status == Status.LIST_OPEN && curY <= startY) {
                        if (dy > weekBar.getBottom() + CalendarView.getmItemHeight()) {
                            return false;
                        } else {
                            return true;
                        }
                    }

                    if (status == Status.LIST_CLOSE && dy <= weekBar.getBottom() + calendarView.getBottom()) {
                        return false;
                    }
                    if (status == Status.LIST_OPEN && dy >= weekBar.getBottom() + CalendarView.getmItemHeight()) {
                        return false;
                    }

                    setMoveTranslationY(curY, selectedRowColumn);
                    return true;
                case TouchEvent.PRIMARY_POINT_UP:
                    curY = ev.getPointerScreenPosition(0).getY();
                    if (status == Status.LIST_CLOSE && dy >= weekBar.getBottom() + calendarView.getBottom()) {
                        if (curY <= downY) {
                            setPointUpAnimation(curY - calendarView.getHeight(), selectedRowColumn);
                            return false;
                        }
                    }
                    if (status != Status.DRAGGING || status == Status.ANIMATING) {
                        return false;
                    }
                    setPointUpAnimation(curY, selectedRowColumn);
            }

            return true;
        });
    }

    protected void changeMonth(int diffMonth) {
        if (diffMonth != 0) {
            calendarView.changeMonth(diffMonth, currentSelectedDate, status);
        } else {
            if (status == Status.LIST_OPEN) {
                calendarView.animateCalendarViewToDate(currentSelectedDate);
            }
            calendarView.animateSelectedViewToDate(currentSelectedDate);
        }
    }


    protected void initWeekBar() {
        weekBar = (DirectionalLayout) findComponentById(ResourceTable.Id_week_bar);
        String[] weeks = new String[0];
        Color redColor = Color.RED;
        try {
            weeks = getResourceManager().getElement(ResourceTable.Strarray_week).getStringArray();
            redColor = new Color(getResourceManager().getElement(ResourceTable.Color_red_ff725f).getColor());
        } catch (IOException | NotExistException | WrongTypeException e) {
            e.printStackTrace();
        }
        Optional<Display> display = DisplayManager.getInstance().getDefaultDisplay(getContext());
        Point pt = new Point();
        display.get().getSize(pt);
        int width = (int) (pt.getPointX() / weeks.length);

        for (int i = 0; i < weeks.length; i++) {
            String week = weeks[i];
            Text textView = new Text(this.getContext());
            textView.setText(week);
            textView.setTextSize(WEEK_ITEM_TEXT_SIZE);
            if (i == 0 || i == weeks.length - 1) {
                textView.setTextColor(redColor);
            } else {
                textView.setTextColor(Color.BLACK);
            }
            textView.setTextAlignment(TextAlignment.CENTER);
            weekBar.addComponent(textView, width, LayoutConfig.MATCH_CONTENT);
        }
    }

    private void animationToTop(CalendarView.SelectedRowColumn selectedRowColumn) {
        status = Status.ANIMATING;

        float curListViewTY = listView.getTranslationY();
        float listViewByY = (float) ((double)calendarView.getHeight() - CalendarView.getmItemHeight() + curListViewTY);
        float curCalendarViewTY = calendarView.getTranslationY();
        float calendarViewByY = (float) ((double)selectedRowColumn.row * CalendarView.getmItemHeight() + curCalendarViewTY);

        AnimatorValue animator = new AnimatorValue();
        animator.setDuration(300);
        animator.setCurveType(Animator.CurveType.LINEAR);
        animator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float v) {
                listView.setTranslationY((float) ((double)curListViewTY - listViewByY * v));
                calendarView.setTranslationY((float) ((double)curCalendarViewTY - calendarViewByY * v));
                if (v >= 1.0) {
                    status = Status.LIST_OPEN;
                }
            }
        });
        animator.start();
    }

    private void animationToBottom() {
        status = Status.ANIMATING;

        float curListViewTY = listView.getTranslationY();
        float listViewByY = (float) (0 + (double)curListViewTY);
        float curCalendarViewTY = calendarView.getTranslationY();
        float calendarViewByY = (float) (0 + (double)curCalendarViewTY);

        AnimatorValue animator = new AnimatorValue();
        animator.setDuration(300);
        animator.setCurveType(Animator.CurveType.LINEAR);
        animator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float v) {
                listView.setTranslationY((float) (curListViewTY - (double)listViewByY * v));
                calendarView.setTranslationY((float) ((double)curCalendarViewTY - calendarViewByY * v));
                if (v >= 1.0) {
                    status = Status.LIST_CLOSE;
                }
            }
        });
        animator.start();
    }

    /**
     * OnCalendarViewItemClickListener
     */
    public interface OnCalendarViewItemClickListener {
        /**
         * <p>when item of Calendar View was clicked will be trigger. </p>
         *
         * @param View               the view(Calendar View Item) that was clicked.
         * @param selectedDate       the date has been selected is "yyyy-MM-dd" type
         * @param listSection        selectedDate would map to the section of ListView, ListView will scroll to specify position(listSection)
         * @param selectedDateRegion selectedDateRegion = SelectedDateRegion.INSIDE was deal with by CalendarListView,you would take care other conditions
         *                           just do anything you want or do nothing.
         *                           <ul>
         *                           <li>if selectedDateRegion = SelectedDateRegion.INSIDE : ListView will scroll to specify position,just care about it.</li>
         *                           <li>if listSection = SelectedDateRegion.BEFORE : imply the selectedDate is before any date in ListView</li>
         *                           <li>if listSection = SelectedDateRegion.AFTER :imply the selectedDate is after any date in ListView</li>
         *                           </ul>
         */
        void onDateSelected(Component View, String selectedDate, int listSection, SelectedDateRegion selectedDateRegion);

        enum SelectedDateRegion {
            BEFORE,
            AFTER,
            INSIDE,
        }

    }

    /**
     * onListPullListener
     */
    public interface onListPullListener {

        void onRefresh();

        void onLoadMore();
    }

    /**
     * OnMonthChangedListener
     */
    public interface OnMonthChangedListener {
        /**
         * when month of calendar view has changed. it include user manually fling CalendarView to change
         * month,also include when user scroll ListView then beyond the current month.it will change month
         * of CalendarView automatically.
         *
         * @param yearMonth the date has been selected is "yyyy-MM-dd" type
         */
        void onMonthChanged(String yearMonth);
    }

    /**
     * Status
     */
    public enum Status {
        // when ListView been push to Top,the status is LIST_OPEN.
        LIST_OPEN,
        // when ListView stay original position ,the status is LIST_CLOSE.
        LIST_CLOSE,
        // when VIEW is dragging.
        DRAGGING,
        //when dragging end,the both CalendarView and ListView will animate to specify position.
        ANIMATING,
    }


}
